/* Copyright (c) 2008-2010 Ondrej Donek.
 * See the file LICENSE.txt for licensing information. */

xtk.include('domutils');
xtk.include('controller');

// Namespace definition
if(typeof(ko) == "undefined") { var ko = {}; }
if(typeof(ko.extensions) == "undefined") { ko.extensions = {}; }
ko.extensions.qwin = {};

/**
 * Qwin JavaScript implementation
 */
(function() {
  // Constants defining modifiers
  const QWIN_MODIFIER_NONE           = "none";
  const QWIN_MODIFIER_ALT            = "alt";
  const QWIN_MODIFIER_CTRL           = "ctrl";
  const QWIN_MODIFIER_SHIFT          = "shift";
  const QWIN_MODIFIER_ALT_CTRL       = "ctrl+alt";
  const QWIN_MODIFIER_ALT_SHIFT      = "alt+shift";
  const QWIN_MODIFIER_CTRL_SHIFT     = "ctrl+shift";
  const QWIN_MODIFIER_CTRL_ALT_SHIFT = "ctrl+alt+shift";

  // Constants defining keysets
  const QWIN_KEYSET_NUMBERS          = "numbers";
  const QWIN_KEYSET_FKEYS            = "fkeys";

  // Constants defining where to open Qwin sidebar
  const QWIN_WHERE_LEFT              = "left";
  const QWIN_WHERE_RIGHT             = "right";

  // Constants defining how to display view names in sidebar's tree
  const QWIN_DISPLAY_FILENAMES_ONLY  = "file-name-only";
  const QWIN_DISPLAY_LAST_FEW_CHARS  = "last-few-chars";
  const QWIN_DISPLAY_FULL_PATHS      = "full-paths";

  // Default count of chars when QWIN_DISPLAY_LAST_FEW_CHARS is used
  const QWIN_DISPLAY_COUNT_OF_CHARS  = 25;

  // Some variables
  var log          = ko.logging.getLogger("ko.extensions.qwin");
  var observerSvc  = Components.classes["@mozilla.org/observer-service;1"].
                     getService(Components.interfaces.nsIObserverService);


  // ========================================================================
  // Preferences

  var prefSrv = Components.classes["@mozilla.org/preferences-service;1"].
      getService(Components.interfaces.nsIPrefService).
      getBranch("extensions.qwin.");
  
  // Only helper function 
  var _ensureBool = function(aVal) { return ((val === true) ? true : false); };
  
  /**
   * Holds object for easy access and manupulation with Qwin preferences
   */
  this.prefs =
  {
    // Getters/Setters
    get enableQwin() { return prefSrv.getBoolPref("enableQwin"); },
    set enableQwin(val) { prefSrv.setBoolPref("enableQwin", _ensureBool(val)); },

    get usedModifier() { return prefSrv.getCharPref("usedModifier"); },
    set usedModifier(val) { prefSrv.setCharPref("usedModifier", val); },

    get usedKeyset() { return prefSrv.getCharPref("usedKeyset"); },
    set usedKeyset(val) { prefSrv.setCharPref("usedKeyset", val); },

    get displayWhere() { return prefSrv.getCharPref("displayWhere"); },
    set displayWhere(val) { prefSrv.setCharPref("displayWhere", val); },

    get pathsDisplay() { return prefSrv.getCharPref("pathsDisplay"); },
    set pathsDisplay(val) { prefSrv.setCharPref("pathsDisplay", val); },

    get pathsDisplayLength() { return prefSrv.getIntPref("pathsDisplayLength"); },
    set pathsDisplayLength(val) { prefSrv.setIntPref("pathsDisplayLength", val); },

    get highlightCurrent() { return prefSrv.getBoolPref("highlightCurrent"); },
    set highlightCurrent(val) { prefSrv.setBoolPref("highlightCurrent", _ensureBool(val)); },

    get showIcons() { return prefSrv.getBoolPref("showIcons"); },
    set showIcons(val) { prefSrv.setBoolPref("showIcons", _ensureBool(val)); },

    get showWebPages() { return prefSrv.getBoolPref("showWebPages"); },
    set showWebPages(val) { prefSrv.setBoolPref("showWebPages", _ensureBool(val)); },

    get showStartPage() { return prefSrv.getBoolPref("showStartPage"); },
    set showStartPage(val) { prefSrv.setBoolPref("showStartPage", _ensureBool(val)); },

    get showOnlyActiveProject() { return prefSrv.getBoolPref("showActiveProjectOnly"); },
    set showOnlyActiveProject(val) { prefSrv.setBoolPref("showActiveProjectOnly", _ensureBool(val)); },

    get showViewsGroups() { return prefSrv.getBoolPref("showViewsGroups"); },
    set showViewsGroups(val) { prefSrv.setBoolPref("showViewsGroups", _ensureBool(val)); },
    
    get showEmptyViewsGroups() { return prefSrv.getBoolPref("showEmptyViewsGroups"); },
    set showEmptyViewsGroups(val) { prefSrv.setBoolPref("showEmptyViewsGroups", _ensureBool(val)); },
    
    get highlightUnsaved() { return prefSrv.getBoolPref("highlightUnsaved"); },
    set highlightUnsaved(val) { prefSrv.setBoolPref("highlightUnsaved", _ensureBool(val)); },

    get highlightUnsavedType() { return prefSrv.getCharPref("highlightUnsavedType"); },
    set highlightUnsavedType(val) { prefSrv.setCharPref("highlightUnsavedType", val); },

    get showSourceOutline() { return prefSrv.getBoolPref("showSourceOutline"); },
    set showSourceOutline(val) { prefSrv.setBoolPref("showSourceOutline", _ensureBool(val)); },
    
    /**
     * Set preferences if not exist (Qwin is running for first time).
     * Called from this.onLoad()
     */
    checkPrefs : function()
    {
      if(!prefSrv.prefHasUserValue("enableQwin"))
        prefSrv.setBoolPref("enableQwin", true);

      if(!prefSrv.prefHasUserValue("usedModifier"))
        prefSrv.setCharPref("usedModifier", QWIN_MODIFIER_ALT_CTRL);

      if(!prefSrv.prefHasUserValue("usedKeyset"))
        prefSrv.setCharPref("usedKeyset", QWIN_KEYSET_FKEYS);

      if(!prefSrv.prefHasUserValue("displayWhere"))
        prefSrv.setCharPref("displayWhere", QWIN_WHERE_LEFT);

      if(!prefSrv.prefHasUserValue("pathsDisplay"))
        prefSrv.setCharPref("pathsDisplay", QWIN_DISPLAY_FILENAMES_ONLY);

      if(!prefSrv.prefHasUserValue("pathsDisplayLength"))
        prefSrv.setIntPref("pathsDisplayLength", QWIN_DISPLAY_COUNT_OF_CHARS);

      if(!prefSrv.prefHasUserValue("highlightCurrent"))
        prefSrv.setBoolPref("highlightCurrent", true);

      if(!prefSrv.prefHasUserValue("showIcons"))
        prefSrv.setBoolPref("showIcons", true);

      if(!prefSrv.prefHasUserValue("showWebPages"))
        prefSrv.setBoolPref("showWebPages", true);

      if(!prefSrv.prefHasUserValue("showStartPage"))
        prefSrv.setBoolPref("showStartPage", true);

      if(!prefSrv.prefHasUserValue("showActiveProjectOnly"))
        prefSrv.setBoolPref("showActiveProjectOnly", false);

      if(!prefSrv.prefHasUserValue("showViewsGroups"))
        prefSrv.setBoolPref("showViewsGroups", false);
      
      if(!prefSrv.prefHasUserValue("showEmptyViewsGroups"))
        prefSrv.setBoolPref("showEmptyViewsGroups", false);

      if(!prefSrv.prefHasUserValue("highlightUnsaved"))
        prefSrv.setBoolPref("highlightUnsaved", true);

      if(!prefSrv.prefHasUserValue("highlightUnsavedType"))
        prefSrv.setCharPref("highlightUnsavedType", "asterix");
      
      if(!prefSrv.prefHasUserValue("showSourceOutline"))
        prefSrv.setBoolPref("showSourceOutline", false/** @todo Still experimental! */);
    } // end checkPrefs()
  };


  // ========================================================================


  /**
   * Prototype object for single Qwin's treeitem
   *
   * @param aIndex {integer}
   * @param aParentIndex {integer}
   * @param aLabel {string}
   * @param aFullPath {string}
   * @param aBaseName {string}
   * @param aIsDirty {boolean}
   * @param aType {string}
   */
  function QwinTreeitemPrototype(aIndex,
                                 aParentIndex, 
                                 aLabel,
                                 aFullPath,
                                 aBaseName,
                                 aIsDirty,
                                 aType)
  {
    this.index       = aIndex;
    this.parentIndex = aParentIndex;
    this.label       = aLabel;
    this.fullPath    = aFullPath;
    this.baseName    = aBaseName;
    this.isDirty     = aIsDirty;
    this.type        = aType;
    this.isOpen      = false;
  };


  /**
   * Helper prototype object for tree sorting
   *
   * @type Object
   */
  var QwinTreesortHelper = {
    /**
     * Contains name of currently sorted column.
     * @type {string}
     */
    mCol : "",

    /**
     * Holds sort direction: -1 is descending, 0 is natural, 1 is ascending.
     * Meaned as private.
     * @type {integer}
     */
    mDirection : 0,

    /**
     * Returns string representing currently used direction
     * @param {string}
     */
    get mDirectionStr(){ return (this.mDirection == 1) ? "ascending" : "descending"; },

    /**
     * Holds count of tree items to examine if tree's row count is changed from
     * last sort (if we can use simple array reverse).
     * @type {integer}
     */
    mFastIndex : 0,

    /**
     * Method for performing sorting.
     * @param {object}
     * @param {object}
     * @returns {integer}
     */
    sort : function(aX, aY) {
      var isIndex = (this.mCol.indexOf("-tree-position-col") > 0) ? true : false;
      var x = (isIndex) ? parseInt(aX.index) : aX.label;
      var y = (isIndex) ? parseInt(aY.index) : aY.label;

      if(x > y) return 1;
      if(x < y) return -1;

      return 0;
    }
  };


  /**
   * Common tree view prototype object. As a parameter is expected used
   * tree view items.
   *
   * @param aTreeitems {array}
   */
  function QwinTreeviewPrototype(aTreeitems) {
    this.originalTreeitems = aTreeitems;
    this.visibleTreeitems  = aTreeitems;
  };
  QwinTreeviewPrototype.prototype =
  {
    treebox   : null,
    selection : null,

    get rowCount() {
      return this.visibleTreeitems.length;
      },
    get idPrefix() {
      return "qwin-" + ko.extensions.qwin.prefs.displayWhere + "-tree";
    },
    get atomSrv() {
      return Components.classes["@mozilla.org/atom-service;1"].
          getService(Components.interfaces.nsIAtomService);
    },

    setTree : function(aOut) {
      this.treebox = aOut;
    },

    getCellText : function(aRow, aCol) {
      if(this.visibleTreeitems[aRow].type == "tabsgroup" &&
         aCol.id == this.idPrefix + "-position-col")
        return " ";
      
      if(aCol.id == this.idPrefix + "-position-col")
        return this.visibleTreeitems[aRow].index;

      if(aCol.id == this.idPrefix + "-path-col")
        return " " + this.visibleTreeitems[aRow].label;

      return "";
    },

    getCellValue : function(aRow, aCol) {
      return (this.visibleTreeitems[aRow].type == "tabsgroup")
          ? null
          : this.visibleTreeitems[aRow].fullPath;
    },

    getCellProperties : function(aRow, aCol, aProp) {
      if(this.visibleTreeitems[aRow].type == "tabsgroup") {
        aProp.AppendElement(this.atomSrv.getAtom("isTabsgroup"));
        return;
      }
      
      if(ko.extensions.qwin.prefs.highlightCurrent) {
        // Fix for bug http://code.google.com/p/qwin/issues/detail?id=6
        var currentView = (ko.views.manager.currentView != null)
            ? ko.views.manager.currentView
            : ko.views.manager.topView;

        if(currentView.document.displayPath == this.visibleTreeitems[aRow].fullPath)
          aProp.AppendElement(this.atomSrv.getAtom("currentView"));
      }
      
      if(ko.extensions.qwin.prefs.highlightUnsaved) {
        if(this.visibleTreeitems[aRow].isDirty) {
          aProp.AppendElement(this.atomSrv.getAtom("isDirty"));
        }
      }
    },

    getImageSrc : function(aRow, aCol) {
      var src = "";

      if(ko.extensions.qwin.prefs.showIcons &&
         aCol.id == this.idPrefix + "-path-col") {
        if(aCol.id == this.idPrefix + "-path-col") {
          if(this.visibleTreeitems[aRow].type == "tabsgroup") {
            src = "chrome://famfamfamsilk/skin/icons/application_double.png";
          } else {
            switch(this.visibleTreeitems[aRow].type) {
              case "browser":
                src = "chrome://famfamfamsilk/skin/icons/page_world.png";
                break;
    
              case "editor":
                src = "chrome://famfamfamsilk/skin/icons/page.png";
                break;
    
              case "startpage":
                src = "chrome://qwin/skin/openkomodo.png";
                break;
            }
          }
        }
      }

      return src;
    },

    hasNextSibling : function(aRow, aAfterIndex) {
      if(aRow == (this.rowCount - 1)) return false;
      
      return (this.visibleTreeitems[aRow].parentIndex == this.visibleTreeitems[aRow + 1].parentIndex)
          ? true
          : false;
    },
    
    getParentIndex : function(aRow) {
      if(!ko.extensions.qwin.prefs.showViewsGroups)
        return -1;
      
      return this.visibleTreeitems[aRow].parentIndex;
    },
    
    getLevel : function(aRow) {
      if(!ko.extensions.qwin.prefs.showViewsGroups)
        return 0;
      
      return (this.visibleTreeitems[aRow].type == "tabsgroup") ? 0 : 1;
    },
    
    isContainer : function(aRow) {
      if(!ko.extensions.qwin.prefs.showViewsGroups) 
        return false;
      
      return (this.visibleTreeitems[aRow].type == "tabsgroup") ? true : false;
    },
    
    isContainerOpen : function(aRow) {
      return this.visibleTreeitems[aRow].isOpen;
    },
    
    isContainerEmpty : function(aRow) {
      if(!ko.extensions.qwin.prefs.showViewsGroups ||
         this.visibleTreeitems[aRow] != "tabsgroup")
        return;
      
      this.visibleTreeitems[aRow].isEmpty;
    },
    
    toggleOpenState : function(aRow) {
      if(!(this.isContainer(aRow))) return;

      if(this.isContainerOpen(aRow)) {
        var delCount = 0;
        var level = this.getLevel(aRow);

        for(var i=aRow+1; i<this.rowCount; i++) {
          if(this.getLevel(i)>level) {
            delCount++;
          } else {
            break;
          }
        }

        if(delCount>0) {
          this.visibleTreeitems.splice(aRow + 1, delCount);
          this.treebox.rowCountChanged(aRow + 1, -delCount);
        }

        this.visibleTreeitems[aRow].isOpen = false;
      } else {
        var label  = this.visibleTreeitems[aRow].label;
        var attach = false;
        
        for(var i=0; i<this.originalTreeitems.length; i++) {
          if(label == this.originalTreeitems) {
            attach = true;
          } else if(attach) {
            this.visibleTreeitems.splice(aRow + (i + 1), -1, this.originalTreeitems[i]);
            this.treebox.rowCountChanged(aRow + 1, 1);
          }
        }
      }
    },

    cycleHeader : function(aCol) {
      var tree   = document.getElementById(this.idPrefix);
      var index  = tree.currentIndex;

      // We have to ensure that all containers are closed
      for(var i=0; i<this.rowCount; i++) 
        if(this.isContainer(i) && this.isContainerOpen(i)) 
          this.toggleOpenState(i);

      if(aCol.id == QwinTreesortHelper.mCol)
        QwinTreesortHelper.mDirection = (QwinTreesortHelper.mDirection == 0)
            ? 1
            : -QwinTreesortHelper.mDirection;

      if(aCol.id == QwinTreesortHelper.mSortedCol &&
         this.rowCount == QwinTreesortHelper.mFastIndex) {
        this.visibleTreeitems.reverse();
      } else {
        if(QwinTreesortHelper.mSortedCol !== "") {
          var old = document.getElementById(QwinTreesortHelper.mSortedCol);
          if(old) old.setAttribute("sortDirection", "");
        }

        QwinTreesortHelper.mSortedCol = aCol.id;
        this.treeitems.sort(QwinTreesortHelper.sort);
      }

      aCol.element.setAttribute("sortDirection", QwinTreesortHelper.mDirectionStr);
      this.treebox.invalidate();

      if(index >= 0) {
        this.selection.select(index);
        this.treebox.ensureRowIsVisible(index);
      }

      QwinTreesortHelper.mFastIndex = this.rowCount;
    },

    isSorted : function() {
      return (QwinTreesortHelper.mDirection != 0) ? true : false;
    },

    // Other (not used) methods
    getColumnProperties : function(aCol, aProp) { return; },
    getRowProperties : function(aRow, aProp) { return; },
    canDrop : function(aRow, aOrientation) { return true; },
    drop : function(aRow, aOrientation) {},
    selectionChanged : function() {},
    cycleCell : function(aRow, aCol) {},
    isEditable : function(aRow, aCol) { return false; },
    setCellText : function(aRow, aCol, aValue) {},
    setCellValue : function(aRow, aCol, aValue) {},
    performAction : function(aAction) {},
    performActionOnRow : function(aAction, aRow) {},
    preformActionOnCell : function(aAction, aRow, aCol) {},
    isSeparator : function(aRow) { return false; }

  }; // End of QwinTreeviewPrototype()


  // ========================================================================
  // Main methods for Qwin
  
  // Below are helper variables used for checking if Qwin's tree needs to update.
  this._last_all_views_count       = 0;
  this._last_topview_views_count   = 0;
  this._last_otherview_views_count = 0;


  /**
   * Fired when Qwin is loading
   *
   * @todo Change way how we capturing keypress events!!!
   */
  this.onLoad = function()
  {
    try {
      // We have default values for all values so user doesn't
      // need to go to the Preferences dialog and set up them firstly.
      this.prefs.checkPrefs();

      // Update Qwin's UI
      this.update(this.prefs.displayWhere);

      // Append event observers
      observerSvc.addObserver(this, "open_file", false);
      observerSvc.addObserver(this, "SciMoz:FileDrop", false);
      observerSvc.addObserver(this, "view_opened", false);
      //observerSvc.addObserver(this, "view_closed", false);
      observerSvc.addObserver(this, "view_closing", false);
      observerSvc.addObserver(this, "current_view_changed", false);
      observerSvc.addObserver(this, "file_changed", false);
      // XXX observerSvc.addObserver(this, "view_list_closed", false);
      
      // Append preferences observer
      prefSrv.QueryInterface(Components.interfaces.nsIPrefBranch2);
      prefSrv.addObserver("nsPref:changed", this, false);

      // Add event listener for capturing keypresses
      // Note: We should definitively use native Komodo's keybindings
      //       and not (Mozilla-like) event listener on events.
      addEventListener("keypress", ko.extensions.qwin.onKeypress, false);
      
      // Append unload handler
      ko.main.addUnloadHandler(this.shutdown, this);
    } catch(e) {
      Components.utils.reportError(e);
      //log.exception(e);
    }
  }; // end onLoad()


  /**
   * Correctly unload Qwin (events obervers). Fired when Komodo is going to
   * be closed.
   */
  this.shutdown = function()
  {
    try {
      observerSvc.removeObserver(this, "open_file");
      observerSvc.removeObserver(this, "SciMoz:FileDrop");
      observerSvc.removeObserver(this, "view_opened");
      //observerSvc.removeObserver(this, "view_closed");
      observerSvc.removeObserver(this, "view_closing");
      observerSvc.removeObserver(this, "current_view_changed");
      observerSvc.removeObserver(this, "file_changed");
      //XXX observerSvc.removeObserver(this, "view_list_closed");
      
      prefSrv.QueryInterface(Components.interfaces.nsIPrefBranch2);
      
      if(prefSrv) {
        prefSrv.removeObserver("nsPref:changed", this);
      }
      
      removeEventListener("keypress", ko.extensions.qwin.onKeypress, false);
    } catch(e) {
      Components.utils.reportError(e);
      //log.exception(e);
    }

    observerSvc = null;
  }; // end shutdown()


  /**
   * Observer imnplementation. We observe changes in views (opening, closing).
   *
   * @param aSubject {string}
   * @param aTopic {string}
   * @param aData {string}
   */
  this.observe = function(aSubject, aTopic, aData)
  {
    try {
      if(!this.prefs.enableQwin) return;
      
      switch(aTopic) {
        case "open_file":
        case "SciMoz:FileDrop":
        case "view_opened":
        //case "view_closed":
        case "view_closing":
        case "current_view_changed":
        case "file_changed":
        // XXX case "view_list_closed":
        case "nsPref:changed":
          //window.setTimeout(function(){ ko.extensions.qwin.update(); }, 23*3);
          this.update();
          break;
      }
    } catch(e) {
      Components.utils.reportError(e);
      //log.exception(e);
    }
  }; // end observe()


  /**
   * Returns array of paths of opened documents
   *
   * @return {array}
   */
  this.getOpenedDocumentsPaths = function()
  {
    var treeitems = new Array();
    var list      = ko.views.manager.topView.getDocumentViewList(true);
    var list2     = ko.views.manager.topView.otherView.getDocumentViewList(true); 
    
    this._last_all_views_count       = list.length;
    this._last_topview_views_count   = list.length;
    this._last_otherview_views_count = list2.length;
    
    try {
      if(!this.prefs.showViewsGroups) {
        // We're ignoring views groups
        var views = list;
        var index = 1;
        
        for(var i=0; i<views.length; i++) {
          var view = views[i];
          var viewtype = view.getAttribute("type");
          
          if(!this._isViewSuitableForTree(view, viewtype)) continue;
          
          treeitems.push(this._createTreeitemFromView(index++, 0, view, viewtype));
        }
      } else {
        // Show views groups (e.g. we're creating nested tree)
        var viewsGrp1  = list;
        var viewsGrp2  = list2;
        var views1 = new Array();
        var views2 = new Array();
        var i = 0;
        
        // Collects all views from the "topView.otherView"
        for(i=0; i<viewsGrp2.length; i++) {
          var view = viewsGrp2[i];
          var viewtype = view.getAttribute("type");
          
          if(!this._isViewSuitableForTree(view, viewtype)) continue;
          
          views2.push({ v: view, t: viewtype });
        }
        
        // Collects all views from the "topView" and skip all which were founded
        // in "topView.otherView".
        for(i=0; i<viewsGrp1.length; i++) {
          var view = viewsGrp1[i];
          var viewtype = view.getAttribute("type");
          
          if(!this._isViewSuitableForTree(view, viewtype)) continue;
          
          var isSame = false;
          for(var ii=0; ii<viewsGrp2.length; ii++) 
            if(view == viewsGrp2[ii])
              isSame = true;
          
          if(isSame) continue;
          
          views1.push({ v: view, t: viewtype });
        }
        
        treeitems.push({ index       : 0,
                         parentIndex : -1,
                         label       : " Current group",
                         isOpen      : true,
                         isSorted    : false,
                         isEmpty     : (views1.length == 0), 
                         type        : "tabsgroup"  });
        
        for(i=0; i<views1.length; i++) {
          treeitems.push(this._createTreeitemFromView(i + 1,
                                                      0,
                                                      views1[i].v,
                                                      views1[i].t));
        }
        
        var cl = treeitems.length;
        treeitems.push({ index       : cl,
                         parentIndex : -1,
                         label       : " Other group",
                         isOpen      : true,
                         isSorted    : false,
                         isEmpty     : (views2.length == 0), 
                         type        : "tabsgroup" });
        
        for(i=0; i<views2.length; i++) {
          treeitems.push(this._createTreeitemFromView(i + (cl + 1),
                                                      0,
                                                      views2[i].v,
                                                      views2[i].t));
        }
      }
    } catch(e) {
      Components.utils.reportError(e);
      //log.exception(e);
    }

    return treeitems;
  }; // end getOpenedDocumentsPaths()
  
  
  /**
   * Checks if given view should be rendered in Qwin's tree
   *
   * @private
   * @param aView {object}
   * @param aViewType {string}
   * @returns {boolean}
   */
  this._isViewSuitableForTree = function(aView, aViewType) {
    // XXX This skips also newly-created and unsaved documents
    if(typeof(aView.document) == "undefined" || !aView.document) return false;
    
    // Check if user want to see Start Page and browsers
    if(!this.prefs.showWebPages && viewtype == "browser") return false;
    if(!this.prefs.showStartPage && viewtype == "startpage") return false;
    
    // XXX Check if user want to display only files of the active project
    //var isFromActivePrj = false;
    //if(this.prefs.showOnlyActiveProject && !isFromActivePrj) continue;
    
    return true;
  }; // end _isViewSuitableForTree(aView, aViewType)
  
  
  /**
   * Creates instance of QwinTreeitemPrototype from the given Komodo's view
   *
   * @private
   * @param aIndex {integer}
   * @param aParentIndex {integer}
   * @param aView {object}
   * @param aViewType {string}
   * @returns {QwinTreeitemPrototype}
   */
  this._createTreeitemFromView = function(aIndex, aParentIndex, aView, aViewType)
  {
    // Create new treeitem
    var fullpath = "*** Untitled ***"
    var basename = "*** Untitled ***"
    var label    = "*** Untitled ***";
    var dirty    = true;

    if(!aView.document.isUntitled) {
      fullpath = aView.document.displayPath;
      basename = aView.document.baseName;
      label    = "";
      dirty    = aView.document.isDirty;
      
      if(aViewType == "startpage") {
        label = "Start Page";
      } else {
        switch(this.prefs.pathsDisplay) {
          case QWIN_DISPLAY_FILENAMES_ONLY:
            label = basename;
            break;

          case QWIN_DISPLAY_LAST_FEW_CHARS:
            var dl = this.prefs.pathsDisplayLength;
            label = "..." + fullpath.substring(fullpath.length - dl,
                                               fullpath.length);
            break;

          case QWIN_DISPLAY_FULL_PATHS:
            label = fullpath;
            break;
        }

        if(dirty && this.prefs.higlightUnsaved)
          if(this.prefs.higlightUnsavedType == "asterix")
            label = label + " *";
      }
    }
    
    return new QwinTreeitemPrototype(aIndex, aParentIndex, label, fullpath,
                                     basename, dirty, aViewType);
  }; // end _createTreeitemFromView(aIndex, aParentIndex, aView, aViewType)
  

  /**
   * Updates Qwin - user preferences and the tree
   */
  this.update = function()
  {
    try {
      var prefix    = ko.extensions.qwin.prefs.displayWhere;
      var tree      = document.getElementById("qwin-" + prefix + "-tree");
      var left_tab  = document.getElementById("qwin_left_tab");
      var right_tab = document.getElementById("qwin_right_tab");

      if(this.prefs.displayWhere == "left") {
        if(left_tab.hasAttribute("collapsed"))
          left_tab.removeAttribute("collapsed");

        right_tab.setAttribute("collapsed", true);
      } else {
        if(right_tab.hasAttribute("collapsed"))
          right_tab.removeAttribute("collapsed");

        left_tab.setAttribute("collapsed", true);
      }

      // Enable/disable Qwin according to user's preferences
      if(this.prefs.enableQwin) {
        tree.disabled = false;
        this.reloadTree();
      } else {
        // When Qwin is disabled we need to ensure that table is empty
        tree.view = new QwinTreeviewPrototype([]);
        tree.disabled = true;
      }
    } catch(e) {
      Components.utils.reportError(e);
      //log.exception(e);
    }
  }; // end update()


  /**
   * Fired whenever we need to reload our tree
   */
  this.reloadTree = function()
  {
    var prefix = ko.extensions.qwin.prefs.displayWhere;
    var tree   = document.getElementById("qwin-" + prefix + "-tree");
    var paths  = this.getOpenedDocumentsPaths();

    tree.view  = new QwinTreeviewPrototype(paths);
  }; // end reloadTree(aPrefix)


  /**
   * Returns currently selected view in tree of active Qwin's sidebar
   */
  this.getCurrentlySelectedView = function()
  {
    try {
      var prefix = ko.extensions.qwin.prefs.displayWhere;
      var tree   = document.getElementById("qwin-" + prefix + "-tree");
      var uri    = tree.view.getCellValue(tree.currentIndex,
                                          tree.columns.getPrimaryColumn());

      return ko.views.manager.getViewForURI(uri);
    } catch(e) {
      Components.utils.reportError(e);
      //log.exception(e);
    }

    return null;
  }; // end getCurrentlySelectedView()


  /**
   * Fired when user doubleclicked on any tree item (document)
   */
  this.onTreeDblClick = function()
  {
    try {
      if(!ko.extensions.qwin.prefs.enableQwin) return;

      var view = this.getCurrentlySelectedView();
      if(!view) return;

      view.makeCurrent(true);
      view.setFocus();
    } catch(e) {
      Components.utils.reportError(e);
      //log.exception(e);
    }
  }; // end onTreeDblClick()
  
  
  /**
   * Returns TRUE whenever Qwin's tree need to update (this is called
   * from ko.extensions.qwin.onKeypress whenever doesn't match
   * the control helper object).
   *
   * @private
   * @returns {boolean}
   */
  this._needUpdate = function() {
    var list      = ko.views.manager.topView.getDocumentViewList(true);
    var list2     = ko.views.manager.topView.otherView.getDocumentViewList(true); 
    
    if(list.length != this._last_all_views_count ||
       list.length != this._last_topview_views_count)
      return true;
    
    if(list2.length != this._last_otherview_views_count)
      return true;
    
    return false;
  }; // end _needUpdate()
  

  /**
   * Fired when user press some key
   *
   * @param aEvent {Components.interfaces.nsIDOMKeyEvent}
   */
  this.onKeypress = function(aEvent)
  {
    if(ko.extensions.qwin._needUpdate()) {
      ko.extensions.qwin.update();
    }
    
  	try {
      var prefix       = ko.extensions.qwin.prefs.displayWhere;
      var tree         = document.getElementById("qwin-" + prefix + "-tree");
  	  var views_count  = tree.view.rowCount;
      var req_modifier = ko.extensions.qwin.prefs.usedModifier;
      var req_keyset   = ko.extensions.qwin.prefs.usedKeyset;
      var req_view     = -1;
      var needShift    = false;
      var needAlt      = false;
      var needCtrl     = false;
      
      switch(req_modifier) {
        case QWIN_MODIFIER_ALT:
          needAlt=true;
          break;
        case QWIN_MODIFIER_CTRL:
          needCtrl=true;
          break;
        case QWIN_MODIFIER_SHIFT:
          needShift=true;
          break;
        case QWIN_MODIFIER_ALT_CTRL:
          needAlt=true;
          needCtrl=true;
          break;
        case QWIN_MODIFIER_ALT_SHIFT:
          needAlt=true;
          needShift=true;
          break;
        case QWIN_MODIFIER_CTRL_SHIFT:
          needCtrl=true;
          needShift=true;
          break;
        case QWIN_MODIFIER_CTRL_ALT_SHIFT:
          needAlt=true;
          needCtrl=true;
          needShift=true;
          break;
      }

      if((needAlt == aEvent.altKey) &&
         (needShift == aEvent.shiftKey) &&
         (needCtrl == aEvent.ctrlKey)) {
        if(req_keyset == QWIN_KEYSET_NUMBERS) {
          if(aEvent.charCode >= 48 && aEvent.charCode <= 57 ||
             aEvent.charCode == 45 || aEvent.charCode == 61) {
            switch(aEvent.charCode) {
              case 48: req_view =  9; break; // "0"
              case 45: req_view = 10; break; // "-"
              case 61: req_view = 11; break; // "="
              default:
                if(views_count < (aEvent.charCode - 49)) return;
                req_view = aEvent.charCode - 49;
                break;
            }
          }
        } else if(req_keyset == QWIN_KEYSET_FKEYS) {
          if(aEvent.keyCode >= 112 && aEvent.keyCode <= 123) {
            if(views_count < (aEvent.keyCode - 112)) return;
            req_view = aEvent.keyCode - 112;
          }
        }
      }

      if(req_view != -1) {
        // Switch the view if requested view was found
        tree.currentIndex = req_view;
        ko.extensions.qwin.onTreeDblClick(null);
        aEvent.stopPropagation();
      }
  	} catch(e) {
  	  Components.utils.reportError(e);
      //log.exception(e);
  	}
  }; // end onKeypress(aEvent)


  /**
   * Implements other Qwin commands. All commands are related
   * to active Qwin's sidebar (left or right doesn't matter)
   *
   * @param aCmd {String}
   */
  this.doCommand = function(aCmd)
  {
    switch(aCmd)
    {
      // Show currently selected view
      case "showView":
        this.onTreeDblClick();
        break;

      // Close currently selected view
      case "closeView":
        var view = this.getCurrentlySelectedView();
        if(view) view.close();
        break;

      // Close all opened views excluding selected one and Start Page
      case "closeAllOthers":
        this.closeAllOtherViews();
        break;

      // Show Qwin's Preferences dialog
      case "showPrefs":
        var dlg = window.open("chrome://qwin/content/preferences.xul", "",
            "chrome,extrachrome,titlebar,toolbar,modal,centerscreen");
        if(dlg) dlg.focus();
        break;
    }
  }; // end doCommand(aCmd)


  /**
   * Go through the opened views and close all views but currently selected
   * and Start Page not.
   */
  this.closeAllOtherViews = function()
  {
    try {
      var current = ko.views.manager.currentView;
      var views   = ko.views.manager.topView.getDocumentViewList(true);
      var index   = 1;

      for(var i=0; i<views.length; i++)
      {
        var view = views[i];
        var viewtype = view.getAttribute("type");

        if(typeof(view.document) == "undefined" || !view.document ||
           viewtype == "startpage")
          continue;

        if(current.document.displayPath != view.document.displayPath)
          view.close();
      }
    } catch(e) {
      Components.utils.reportError(e);
      //log.exception(e);
    }
  }; // end closeAllOtherViews()

}).apply(ko.extensions.qwin);

// ===========================================================================

// Load properly panel overlays

// Initialize it once Komodo has finished loading
addEventListener("load",
                 function(){setTimeout("ko.extensions.qwin.onLoad()", 3000);},
                 false);
